﻿using Newtonsoft.Json;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Reflection.Metadata;

namespace ForguncyPluginPackageTool
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Begin...");
            if (args.Length < 2)
            {
                Console.WriteLine("--------Args count is not correct. Args count: " + args.Length);
                Console.WriteLine(string.Join("\r\n", args));
                Console.WriteLine("--------");
            }

            var projectPath = args[0];
            var assemblyPath = args[1];
            bool autoInstall = true;
            if (args.Length > 2)
            {
                autoInstall = args[2]?.ToLower() == "debug";
            }

            projectPath = Path.GetFullPath(projectPath);
            Console.WriteLine("projectPath:" + projectPath);
            Console.WriteLine("assemblyPath:" + assemblyPath);
            Console.WriteLine("autoInstall:" + autoInstall);

            var forguncyVersion = GetReferForguncyVersion(assemblyPath);

            Console.WriteLine("ForguncyVersion: " + forguncyVersion);

            var temp = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
            Directory.CreateDirectory(temp);
            try
            {
                File.Copy(Path.Combine(projectPath, "PluginConfig.json"), Path.Combine(temp, "PluginConfig.json"));
                MakeFileNotReadOnly(Path.Combine(temp, "PluginConfig.json"));

                if (File.Exists(Path.Combine(projectPath, "LICENSE.txt")))
                {
                    File.Copy(Path.Combine(projectPath, "LICENSE.txt"), Path.Combine(temp, "LICENSE.txt"));
                    MakeFileNotReadOnly(Path.Combine(temp, "LICENSE.txt"));
                }

                CopyFolder(Path.Combine(projectPath, "Resources"), Path.Combine(temp, "Resources"));

                ClearFolder(temp, projectPath);

                CopyFolder(Path.Combine(projectPath, "bin"), Path.Combine(temp), new HashSet<string>() {
                    "GrapeCity.Forguncy.Plugin.dll",
                    "Forguncy.Commands.dll",
                    "GrapeCity.Forguncy.ServerApi.dll",
                    "GrapeCity.Forguncy.CellTypes.dll"
                });

                var pluginItem = CreatePluginItem(Path.Combine(temp, "PluginConfig.json"));
                var frontEndLocalResourceGenerator = new FrontEndLocalResourceGenerator(projectPath, temp, pluginItem);
                if (frontEndLocalResourceGenerator.ShouldGenerate)
                {
                    frontEndLocalResourceGenerator.Generate();
                }
                    

                var fileName = Path.GetFileName(projectPath);

                var zipFile = Path.Combine(projectPath, "bin", fileName + ".zip");

                if (File.Exists(zipFile))
                {
                    MakeFileNotReadOnly(zipFile);
                    File.Delete(zipFile);
                }

                ZipFile.CreateFromDirectory(temp, zipFile);

                File.WriteAllText(Path.Combine(temp, "SourceCodePath"), projectPath);

                if (autoInstall)
                {
                    AutoInstallNewPlugin(Path.Combine(projectPath, "PluginConfig.json"), forguncyVersion, temp);
                }
            }
            finally
            {
                Directory.Delete(temp, true);
            }
        }

        private static void AutoInstallNewPlugin(string configPath, Version forguncyVersion, string tempFolder)
        {
            var pluginItem = CreatePluginItem(configPath);
            var fileName = Path.GetFileName(Path.GetDirectoryName(configPath));
            var baseFileName = fileName;
            MarkPluginRemoved((i) => ShouldRemovePluginFolderWhenReplace(pluginItem, i), forguncyVersion);
            var targetFolder = Path.Combine(GetPluginFolder(forguncyVersion), fileName);
            var index = 1;
            while (Directory.Exists(targetFolder))
            {
                fileName = baseFileName + index.ToString();
                targetFolder = Path.Combine(GetPluginFolder(forguncyVersion), fileName);
                index++;
            }
            CopyFolder(tempFolder, targetFolder);
            ClearFolder(targetFolder, tempFolder);
        }

        public static void MakeFileNotReadOnly(string fileName)
        {
            if (File.Exists(fileName))
            {
                if ((File.GetAttributes(fileName) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
                {
                    File.SetAttributes(fileName, File.GetAttributes(fileName) & ~FileAttributes.ReadOnly);
                }
            }
        }

        public static void CopyFolder(string sourcePath, string destinationPath, HashSet<string> skipfiles = null)
        {
            if (!Directory.Exists(destinationPath))
            {
                Directory.CreateDirectory(destinationPath);
            }

            //Now Create all of the directories
            foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories))
            {
                Directory.CreateDirectory(dirPath.Replace(sourcePath, destinationPath));
            }

            //Copy all the files & Replaces any files with the same name
            foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories))
            {
                if (skipfiles != null)
                {
                    if (skipfiles.Contains(Path.GetFileName(newPath)))
                    {
                        continue;
                    }
                }
                if (Path.GetExtension(newPath) == ".pdb" ||
                    Path.GetExtension(newPath) == ".ts" ||
                    Path.GetFileName(newPath).ToLower().EndsWith(".js.map") ||
                    Path.GetExtension(newPath) == ".zip")
                {
                    continue;
                }
                var newFile = newPath.Replace(sourcePath, destinationPath);

                File.Copy(newPath, newFile, true);

                MakeFileNotReadOnly(newFile);
            }
        }

        public static void ClearFolder(string dir, string sourceDir)
        {
            foreach (var file in Directory.GetFiles(dir))
            {
                var filename = Path.GetFileName(file);

                if (!File.Exists(Path.Combine(sourceDir, filename)))
                {
                    File.Delete(file);
                }
            }

            foreach (var directoriy in Directory.GetDirectories(dir))
            {
                var directoriyName = Path.GetFileName(directoriy);

                var directoriyPath = Path.Combine(sourceDir, directoriyName);

                if (!Directory.Exists(directoriyPath))
                {
                    Directory.Delete(directoriy, true);
                }
                else
                {
                    ClearFolder(directoriy, directoriyPath);
                }
            }
        }

        private static bool ShouldRemovePluginFolderWhenReplace(PluginItem newItem, PluginItem deletedItem)
        {
            if (newItem.guid != deletedItem.guid)
            {
                return false;
            }
            // 相同guid的其他插件应该删除，设计器可能加载的是之前的插件。
            return true;
        }

        private static void MarkPluginRemoved(Predicate<PluginItem> needRemoveFunc, Version version)
        {
            var configs = Directory.GetFiles(GetPluginFolder(version), "PluginConfig.json", SearchOption.AllDirectories);
            foreach (var config in configs)
            {
                PluginItem pluginItem;
                try
                {
                    pluginItem = CreatePluginItem(config);
                }
                catch (FileNotFoundException)
                {
                    // 编译过程中，可能config文件已经被删除了
                    continue;
                }
                catch (DirectoryNotFoundException)
                {
                    // 编译过程中，可能config文件已经被删除了
                    continue;
                }
                catch (IOException)
                {
                    // 编译过程中，可能config文件已经被删除了
                    continue;
                }
                if (needRemoveFunc(pluginItem))
                {
                    var oldFolder = Path.GetDirectoryName(config);
                    if (CheckAllDllFilesDeletable(oldFolder))
                    {
                        Directory.Delete(oldFolder, true);
                        continue;
                    }

                    Console.WriteLine($"Uninstall old plugin from {oldFolder} failed. Will add delete marker");

                    // 如果现有的插件无法直接删除，就添加删除标记
                    if (!File.Exists(GetDeletedMarkerFileName(config)))
                    {
                        File.Create(GetDeletedMarkerFileName(config));
                    }
                }
            }
        }
        public static string GetPluginFolder(Version version)
        {
            var folder = Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments);

            var pluginFolderName = string.Format("ForguncyPlugin{0}", "v" + version.Major.ToString() + (version.Build >= 100 ? ".1" : ""));

            return Path.Combine(folder, pluginFolderName);
        }

        public static PluginItem CreatePluginItem(string config)
        {
            var text = File.ReadAllText(config);
            var item = JsonConvert.DeserializeObject<PluginItem>(text);
            item.configFilePath = config;
            return item;
        }
        public static string GetDeletedMarkerFileName(string config)
        {
            return Path.Combine(Path.GetDirectoryName(config), "Fgc_deleted_mark");
        }

        public static Version GetReferForguncyVersion(string assemblyPath)
        {
            var assembly = Assembly.LoadFile(assemblyPath);
            var forguncyPluginAssemblyName = assembly.GetReferencedAssemblies().FirstOrDefault(i => i.Name.StartsWith("GrapeCity.Forguncy.Plugin"));
            if (forguncyPluginAssemblyName == null)
            {
                forguncyPluginAssemblyName = assembly.GetReferencedAssemblies().FirstOrDefault(i => i.Name.StartsWith("Forguncy.Commands"));
            }
            if (forguncyPluginAssemblyName == null)
            {
                forguncyPluginAssemblyName = assembly.GetReferencedAssemblies().FirstOrDefault(i => i.Name.StartsWith("GrapeCity.Forguncy.CellTypes"));
            }
            return forguncyPluginAssemblyName.Version;
        }

        static bool CheckAllDllFilesDeletable(string folderPath)
        {
            string[] files = Directory.GetFiles(folderPath, "*.dll");

            foreach (string file in files)
            {
                try
                {
                    using (FileStream stream = File.Open(file, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
                    {

                    }
                }
                catch (IOException)
                {
                    // 文件被其他进程锁定
                    return false;
                }
            }

            return true;
        }
    }
}